[QT

您所在的位置:网站首页 qfile open失败 [QT

[QT

2024-05-28 08:20| 来源: 网络整理| 查看: 265

本文转自:《Qt编程指南》        作者:奇先生

Qt编程指南,Qt新手教程,Qt Programming Guide

7.2 基本文件读写QFile

Qt 常见的文件读写类有三个 QFile、QTextStream 和 QDataStream,本节先概要介绍这三个文件读写类,然后详细介绍 QFile 类的内容,从 QFile 的基类讲起,介绍 QFile 关于文件属性、权限方面的函数和实际中常用的读写函数,最后通过两个例子展示 QFile 类的用法,第一个例子是读写 Unix/Linux 常见配置文件,解析文本形式的配置项和数值;第二个例子是读取 BMP 图片文件头,解析字节数组(结构体)形式的文件。  

7.2.1 三个常用文件读写类概览

QFile、QTextStream 和 QDataStream 三个类的关系可以用下图来说明:

QFile 是基本的文件读写类,它的主要功能其实就是负责打开文件,虽然它自己有读写文件中字节、字节数组的函数,但是直接用 QFile 类的接口函数读写文件的情况是相对少见的,因为 QFile 的读写函数功能比较简单,就是面向字节数据进行读写。 C++ 和 Qt 常用的类型比如 int、double、QString、QRect等,都不是简单 char * 和 char 类型,如果用 QFile 读写这些常用数据类型,要手动转成 char * 来读写,比较麻烦。Qt 更为常用的文件读写类是 QTextStream 和 QDataStream ,这两个类都有类似的构造函数,构造函数是以 QFile * 指针为参数,它们为 C++ 和 Qt 常用的数据类型读写操作做了封装,类似标准 C++ 的 iostream 和 fstream。使用 QTextStream 和 QDataStream 之前,要先定义 QFile 对象,打开指定文件,然后根据 QFile 对象指针构造高级的读写类 QTextStream 和 QDataStream 对象,然后就可以用输入输出流的操作符 >> 和 setupUi(this); //定义 IPv4 正则表达式,注意 "\\" 就是一个反斜杠 QRegExp re("^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}" "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"); //新建正则表达式验证器 QRegExpValidator *reVali = new QRegExpValidator(re); //设置给 lineEditIP ui->lineEditIP->setValidator(reVali); //新建整数验证器 QIntValidator *intVali = new QIntValidator(0, 65535); //设置给 lineEditPort ui->lineEditPort->setValidator(intVali); } Widget::~Widget() { delete ui; }

包含的头文件里面,和 是正则表达式和正则表达式验证器,用于校验 IPv4 输入, 是整数验证器,用于校验端口范围。 是文件对话框,用于获取打开或保存的文件名, 是文件类。 在构造函数里面,定义了一个 IPv4 正则表达式 re,注意之前 5.2.4 节 netparas 例子也用过这个表达式,当时编的正则表达式代码有点问题,因为其他脚本语言 "\" 不是转义字符,是原始的反斜杠,在 C++ 代码中的字符串,需要用 "\\" 替换 "\" 来表示字符串中的一个反斜杠字符。目前把正则表达式都矫正了,这回是对的了。 在定义 re 之后,新建正则表达式验证器 reVali,并把验证器设置给 lineEditIP。 接着新建整数验证器 intVali ,设置给 lineEditPort。 构造函数新增的代码就是为了验证 IPv4 格式和端口范围。 在我们要打开文件时,需要在文件系统里浏览找到这个文件,可以用类似下面的代码实现,就是 "浏览源" 按钮的槽函数:

void Widget::on_pushButtonBrowseSrc_clicked() { //获取将要打开的文件名 QString strSrcName = QFileDialog::getOpenFileName( this, tr("打开配置文件"), tr("."), tr("Text files(*.txt);;All files(*)") ); if( strSrcName.isEmpty() ) { //空字符串不处理,返回 return; } else { //设置源文件名 ui->lineEditSrcFile->setText(strSrcName); } }

这个函数里最关键的就是 QFileDialog::getOpenFileName() 静态函数,一般只需要设置该静态函数的四个参数: 第一个参数是父窗口指针;第二个是弹出对话框的标题;第三个是弹出对话框默认的开始路径;第四个是文件扩展名过滤字符串。过滤字符串格式就如代码里显示的,多个过滤器用双分号分隔,扩展名格式放在圆括号内部。如果找到文件名成功,该函数就会返回正常的文件名字符串,如果用户取消了对话框,那么返回空字符串。因此需要判断一下对话框的返回字符串是否为空,然后把非空字符串设置到界面的控件显示出来。 接下来是 "加载配置" 按钮对应的槽函数代码:

void Widget::on_pushButtonLoad_clicked() { //获取源文件名 QString strSrc = ui->lineEditSrcFile->text(); if(strSrc.isEmpty()) { //没设置文件名 return; } //定义文件对象 QFile fileIn(strSrc); //判断是否正确打开 if( ! fileIn.open( QIODevice::ReadOnly ) ) { //打开错误 QMessageBox::warning(this, tr("打开错误") , tr("打开文件错误:") + fileIn.errorString()); return; //不处理文件 } //读取并解析文件 while( ! fileIn.atEnd() ) { //读取一行 QByteArray baLine = fileIn.readLine(); baLine = baLine.trimmed(); //剔除字符串两端空白 //判断是否为注释行 if( baLine.startsWith('#') ) { continue; //不处理注释行 } //正常的设置行,分析这一行的配置项 AnalyzeOneLine(baLine); } //提示加载完毕 QMessageBox::information(this, tr("加载配置"), tr("加载配置项完毕!")); }

这个槽函数先获取源文件名,判断是否为空,没有文件名就不处理。有文件名,就根据文件名定义文件类对象 fileIn,以 QIODevice::ReadOnly 模式打开文件,如果打开失败就报错并返回,错误信息可以用 fileIn.errorString() 函数获取。 如果打开正确,就用 while 循环加载文件的每一行,直到文件结束。 因为配置文件里面的文本行,两端可能有空白字符,先剔除掉两端空白字符,然后判断打头的字符是否为井号,如果是井号就跳过这个注释行。如果打头的不是#号,说明是有用的配置行,就调用 AnalyzeOneLine() 分析这一行配置。分析完配置文件所有行之后,我们用QMessageBox::information() 提示已经加载完毕。 在文本处理时,要注意用户可能输入的空白字符,通常需要把文本两端的空白字符剔除掉再进行后续的判断,QString 类和 QByteArray 类都有 trimmed() 函数实现剔除两端空白字符的功能。 接着我们看看私有函数 AnalyzeOneLine() 的代码:

//分析一行配置文本,设置到对应的控件里 void Widget::AnalyzeOneLine(const QByteArray &baLine) { //按等号分隔 QList list = baLine.split('='); if(list.count() < 2) { //分隔之后没有配置值,无法设置 return; } //配置项名,剔除空格,变为统一的小写名称 QByteArray optionName = list[0].trimmed().toLower(); //配置项的值,只需要剔除空格 QByteArray optionValue = list[1].trimmed(); QString strValue = QString::fromLocal8Bit( optionValue ); //判断哪个配置项 if( "ip" == optionName ) { ui->lineEditIP->setText( strValue ); return; } if( "port" == optionName) { ui->lineEditPort->setText( strValue ); return; } if( "hostname" == optionName ) { ui->lineEditHostName->setText( strValue ); return; } if( "workgroup" == optionName) { ui->lineEditWorkGroup->setText( strValue ); return; } //如果有其他不相关的配置项名不处理 }

配置文本的每一行都是等号分隔的,左边为配置项名,右边为配置项的数值。 我们直接用 split() 函数切分就行了,切分之后得到列表 list。判断 list 是否把文本行切成了两段以上,如果少于两段,说明配置行内容不完整,配置项名或数值少了,就不处理。如果切分后,至少有两段文本,那么把 list[0] 剔除空白,并转为全小写字母,作为配置项名称,转成小写防止用户把配置项名写成大写字母导致与后面的判断不匹配。 再把 list[1] 剔除两端空白,并构造新的数值字符串 strValue ,用于界面显示。 我们对配置项的名称进行判断,对于 "ip" 、"port"、"hostname"、"workgroup" 四个配置项名,匹配哪个就将数值字符串设置到对应的控件里面。如果有不匹配的配置项名,这里没有处理,我们只处理想要的配置项。 文件打开和加载的代码就是上面那么多。下面来看看如何保存新的配置文件。 获取保存文件名的代码如下所示,就是 "浏览目的" 按钮的槽函数:

void Widget::on_pushButtonBrowseDst_clicked() { //获取要保存的文件名 QString strDstName = QFileDialog::getSaveFileName( this, tr("保存配置文件"), tr("."), tr("Text files(*.txt);;All files(*)") ); if(strDstName.isEmpty()) { //空字符串不处理 return; } else { //设置要保存的文件名 ui->lineEditDstFile->setText(strDstName); } }

获取保存文件名就用 QFileDialog::getSaveFileName() 静态函数,也是设置四个参数,与打开文件对话框非常类似。注意保存文件对话框会提示是否覆盖已存在的文件,而打开文件对话框总是选择已存在文件进行打开。一般保存文件对话框都是用户自己设置不存在的新文件名用于保存文件。 获取到非空的保存文件名字符串之后,设置给 lineEditDstFile 控件显示。 最后是实际保存各个配置项的函数,就是 "保存配置" 按钮对应的槽函数:

void Widget::on_pushButtonSave_clicked() { //获取保存文件名 QString strSaveName = ui->lineEditDstFile->text(); //获取设置值 QString strIP = ui->lineEditIP->text(); QString strPort = ui->lineEditPort->text(); QString strHostName = ui->lineEditHostName->text(); QString strWorkGroup = ui->lineEditWorkGroup->text(); //如果字符串有空串就不写入 if( strSaveName.isEmpty() || strIP.isEmpty() || strPort.isEmpty() || strHostName.isEmpty() || strWorkGroup.isEmpty() ) { QMessageBox::warning(this, tr("保存配置"), tr("需要设置好保存文件名和所有配置项数值。")); return; } //定义文件对象 QFile fileOut(strSaveName); //打开 if( ! fileOut.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { QMessageBox::warning(this, tr("打开文件"), tr("打开目的文件失败:") + fileOut.errorString() ); return; } //构造字节数组写入 //ip 行 QByteArray baTemp = "ip = "; baTemp += strIP.toLocal8Bit() + "\n"; fileOut.write( baTemp ); //port 行 baTemp = "port = "; baTemp += strPort.toLocal8Bit() + "\n"; fileOut.write( baTemp ); //hostname 行 baTemp = "hostname = "; baTemp += strHostName.toLocal8Bit() + "\n"; fileOut.write( baTemp ); //workgroup 行 baTemp = "workgroup = "; baTemp += strWorkGroup.toLocal8Bit() + "\n"; fileOut.write( baTemp ); //提示保存成功 QMessageBox::information(this, tr("保存配置"), tr("保存配置项成功!")); }

这个槽函数先获取保存文件名和四个配置项的数值字符串,如果有空字符串就不保存,必须填好保存文件名和所有配置项才进行后面操作。 根据保存文件名定义一个文具类对象 fileOut,然后以 “写入、清空旧数据、文本” 三个模式同时打开文件,如果打开失败就提示错误消息 fileOut.errorString()。如果打开成功,就开始写入,定义临时字节数组 baTemp ,先构造 ip 配置行的前半截,然后把 strIP 转为本地化字符串,并附加上换行符,这样构造了一行配置文本,写入到新配置文件中。 其他三个配置项的行类似构造,然后都写入到新配置文件中。 写入时,换行符要自己添加,因为 write() 函数是不会自动添加换行符的。 写完四行配置文本之后,用 QMessageBox::information() 提示保存配置成功。 我们生成并运行例子,选择项目文件夹中的 testconf.txt 加载,得到如下图效果:

保存配置功能这里不截图了,读者可以自己试试看。 这第一个示例是按行读取文本文件,并分析配置项内容。下面第二个例子学习如何按字节块读取结构体形式的文件头。  

7.2.5 BMP文件头解析示例

BMP 是比较简单的图片文件,Windows 画图板默认就是以这种格式保存图片。在 MFC 和 Windows 编程的书籍里面有不少关于 BMP 文件头的介绍,这里简单介绍一下。BMP 文件的大致框架如下图所示:

我们本小节关注的就是打头的两个结构体 BMPFileHeader 和 BMPInfoHeader,后面颜色表和像素点数据就不读取了。其实所有的文件格式都是由各种各样的结构体组成,BMP 也是这样。BMP 文件首先以一个 BMPFileHeader 开始,共14字节,我们按照 Qt 风格,把 BMPFileHeader 定义如下:

//定义文件头 BMPFileHeader,长度14字节 struct BMPFileHeader {     quint16 bfType; //文件类型,原始两字节 'BM'     quint32 bfSize; //BMP图片文件大小     quint16 bfReserved1;    //保留字段1,数值为 0     quint16 bfReserved2;    //保留字段2,数值为 0     quint32 bfOffBits;      //像素点数据起始位置,相对于 BMPFileHeader 的偏移量,以字节为单位 };

文件头两个字节就是 'B' 和 'M' ,对应的 bfType 短整形数值为 0x4D42 ,读取文件的时候前面的字节会填充到低位字节,所以倒了一下。bfSize 就是图片文件的大小,其他三个字段我们本小节不关心。 BMP文件第二个部分是图片信息头 BMPInfoHeader,我们把该结构体按 Qt 风格定义如下:

//定义信息头 BMPInfoHeader,长度40字节 struct BMPInfoHeader {     quint32 biSize; //本结构体长度,占用字节数     quint32 biWidth;    //图片宽度,像素点数     quint32 biHeight;   //图片高度,像素点数     quint16 biPlanes;   //目标设备级别,数值为 1 (图层数或叫帧数)     quint16 biBitCount; //每个像素点占用的位数,就是颜色深度 (位深度)     quint32 biCompression; //是否压缩,一般为 0 不压缩     quint32 biSizeImage;   //像素点数据总共占用的字节数,因为每行像素点数据末尾会按4字节对齐,对齐需要的字节数也算在内     quint32 biXPelsPerMeter;//水平分辨率,像素点数每米(== 水平DPI * 39.3701)     quint32 biYPelsPerMeter;//垂直分辨率,像素点数每米(== 垂直DPI * 39.3701)     quint32 biClrUsed;      //颜色表中实际用到的颜色数目     quint32 biClrImportant;//图片显示中重要颜色数目 };

这里面大部分字段比较专业,如果没学图像处理课程可能不太好理解,本小节主要关心图片宽度 biWidth、高度 biHeight、帧数 biPlanes、颜色深度 biBitCount、水平分辨率 biXPelsPerMeter、垂直分辨率 biYPelsPerMeter。我们例子程序解析之后的 BMP 头部信息大致与 Windows 系统对 BMP 文件属性的描述类似:

示例图片 logo.bmp 可以到网站目录里下载:bmpheader 下面开始这个例子,我们重新打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写: ①项目名称 bmpheader,创建路径 D:\QtProjects\ch07,点击下一步; ②套件选择里面选择全部套件,点击下一步; ③基类选择 QWidget,点击下一步; ④项目管理不修改,点击完成。 建好项目之后,打开窗体 widget.ui 文件,进入设计模式,把主窗体大小设置为 480*360 。 然后按照下图排布,拖入三大行控件:

第一行控件:标签控件,文本为 "文件名";单行编辑器,对象名为 lineEditName;按钮控件,文本为 "浏览",对象名为 pushButtonBrowse。第一行按照水平布局器布局。 第二行控件:按钮控件,文本为 "显示图片",对象名为 pushButtonShowPic;水平弹簧条,对象名 horizontalSpacer;按钮控件,文本为 "读取头部",对象名为 pushButtonReadHeader。第二行控件也是按水平布局器布局。 第三行控件按如下步骤拖动: ① 拖一个 Scroll Area 滚动区域控件到左边,对象名默认为 scrollArea,把它拉大,占据左边大部分区域; ② 拖一个标签控件到 scrollArea 控件内部,标签文本为 "显示图片区域",对象名为 labelShowPic; ③ 拖一个 Text Browser 控件到右边空白区域,这个丰富文本浏览控件对象名默认为 textBrowser,这个文本浏览控件用于显示我们程序解析的 BMP 头部信息。 第三行的控件就是这些,关于第三行布局,按下面来操作: ● 点击选中 labelShowPic 标签,设置它的 sizePolicy 属性中水平策略和垂直策略都为 Expanding, 设置它的 alignment 属性为 水平的 AlignHCenter 和 垂直的 AlignVCenter:

● 点击选中 scrollArea 控件,点击上面的水平布局按钮,这样是为滚动区域内部设置主布局器,滚动区域内的主布局器只有一个标签控件 labelShowPic,这个标签控件会填充满整个滚动区域。 设置滚动区域内部主布局器之后,滚动区域会自动缩小,我们把滚动区域重新拉大,大概拉大成下图的样子,然后设置scrollArea 控件 sizePolicy 属性里面的水平伸展为 3,如下图所示:

● 点击选中右边的 textBrowser 控件,然后设置它的 sizePolicy 属性里面的水平伸展为 1 ,如下图所示:

● 按照上面的设置后,第三行的控件细节就配置好了,我们选中第三行的 scrollArea 和 textBrowser ,点击上面的水平布局按钮,进行水平布局,这时候滚动区域控件和文本浏览控件自动按照 3:1 比例分配水平空间:

● 我们点击主窗体的空白区域,只选中主窗体,而不选中任何控件,点击上面的垂直布局按钮,为主窗体设置主布局器:

按照上面设计好界面之后,为三个按钮都添加 clicked() 信号对应的槽函数:

添加好三个槽函数之后,我们保存界面文件,关闭界面文件,回到 QtCreator 编辑模式。 我们打开 widget.h 头文件,向其中添加结构体声明代码:

#ifndef WIDGET_H #define WIDGET_H #include //结构体都按 1 字节补齐,因为编译器默认按 4 字节补齐,导致 BMPFileHeader 长度 16,16是错的 #pragma pack(1) //定义文件头 BMPFileHeader,长度14字节 struct BMPFileHeader { quint16 bfType; //文件类型,原始两字节 'BM' quint32 bfSize; //BMP图片文件大小 quint16 bfReserved1; //保留字段1,数值为 0 quint16 bfReserved2; //保留字段2,数值为 0 quint32 bfOffBits; //像素点数据起始位置,相对于 BMPFileHeader 的偏移量,以字节为单位 }; //定义信息头 BMPInfoHeader,长度40字节 struct BMPInfoHeader { quint32 biSize; //本结构体长度,占用字节数 quint32 biWidth; //图片宽度,像素点数 quint32 biHeight; //图片高度,像素点数 quint16 biPlanes; //目标设备级别,数值为 1 (图层数或叫帧数) quint16 biBitCount; //每个像素点占用的位数,就是颜色深度 (位深度) quint32 biCompression; //是否压缩,一般为 0 不压缩 quint32 biSizeImage; //像素点数据总共占用的字节数,因为每行像素点数据末尾会按4字节对齐,对齐需要的字节数也算在内 quint32 biXPelsPerMeter;//水平分辨率,像素点数每米(== 水平DPI * 39.3701) quint32 biYPelsPerMeter;//垂直分辨率,像素点数每米(== 垂直DPI * 39.3701) quint32 biClrUsed; //颜色表中实际用到的颜色数目 quint32 biClrImportant;//图片显示中重要颜色数目 }; namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); ~Widget(); private slots: void on_pushButtonBrowse_clicked(); void on_pushButtonShowPic_clicked(); void on_pushButtonReadHeader_clicked(); private: Ui::Widget *ui; }; #endif // WIDGET_H

头文件后半部分是 Widget 类的代码,是自动生成的。我们主要看看前半截的结构体声明。在声明结构体之前,有一句代码:

#pragma  pack(1)

这句代码的意思就是告知编译器对结构体里面的变量按 1 字节对齐,一般编译器默认是 4 字节对齐。4字节对齐导致 BMPFileHeader 里面的 bfType 占用 4 字节,然后这个结构体就变成 16 字节长度。16字节长度是错误的设定,因为真正的头部只有 14 字节,必须引入上面一句对齐调整代码,后面的文件头读写才会正常。 BMPFileHeader 和 BMPInfoHeader 结构体声明照着上面代码抄就行了,具体含义不讲了,一般也就图像处理方面的课程才用到,这里就当它 们是一堆变量就行了。这两个结构体在 BMP 文件是顺序存放的,所以 BMP 文件结构是比较简单的,读者以后编程有可能遇到其他复杂文件格式,可能出现结构体嵌套、链接之类的,就比较麻烦了。 接下来我们编辑 widget.cpp,添加功能代码,首先是头文件包含和构造函数:

#include "widget.h" #include "ui_widget.h" #include #include #include #include #include Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); //打印结构体大小 qDebug()text(); if( strName.isEmpty() ) { return; } else { //在 labelShowPic 标签控件显示图片 ui->labelShowPic->setPixmap( QPixmap(strName) ); } }

这个槽函数先获取图片文件名 strName,如果文件名非空,那就把文件加载为 QPixmap 像素图,然后设置给 labelShowPic 标签控件,显示该图片。 最后是 "读取头部" 按钮对应的槽函数代码:

void Widget::on_pushButtonReadHeader_clicked() { //获取图片文件名 QString strName = ui->lineEditName->text(); if( strName.isEmpty() ) { //没文件名 return; } //现在有文件名 QFile fileIn(strName); //只读模式打开 if( ! fileIn.open(QIODevice::ReadOnly) ) { QMessageBox::warning(this, tr("打开文件"), tr("打开文件失败:") + fileIn.errorString()); return; } //现在正确打开了 //定义 BMP 文件头 BMPFileHeader bfh; //定义 BMP 信息头 BMPInfoHeader bih; //读取 BMP 文件头和信息头 qint64 nReadBFH = fileIn.read( (char*)&bfh, sizeof(bfh) ); qint64 nReadBIH = fileIn.read( (char*)&bih, sizeof(bih) ); //判断读取字节数对不对 if( (nReadBFH < sizeof(bfh)) || (nReadBIH < sizeof(bih)) ) { QMessageBox::warning(this, tr("读取 BMP"), tr("读取 BMP 头部失败,头部字节数不够!")); return; } //字节数目够了,构造信息字符串 QString strInfo = tr("文件名:%1\r\n\r\n").arg(strName); QString strTemp; //开始判断 if( bfh.bfType != 0x4D42 ) { strTemp = tr("类型:不是 BMP 图片\r\n"); strInfo += strTemp; } else { //是正常的 BMP 图片 strTemp = tr("类型:是 BMP 图片\r\n"); strInfo += strTemp; //读取 bih 里面的信息 strTemp = tr("宽度:%1\r\n").arg( bih.biWidth ); strInfo += strTemp; strTemp = tr("高度:%1\r\n").arg( bih.biHeight ); strInfo += strTemp; strTemp = tr("水平分辨率:%1 DPI\r\n").arg( (int)(bih.biXPelsPerMeter/39.3701) ); strInfo += strTemp; strTemp = tr("垂直分辨率:%1 DPI\r\n").arg( (int)(bih.biYPelsPerMeter/39.3701) ); strInfo += strTemp; strTemp = tr("颜色深度:%1 位\r\n").arg( bih.biBitCount ); strInfo += strTemp; strTemp = tr("帧数:%1\r\n").arg(bih.biPlanes); strInfo += strTemp; } //显示信息串到 textBrowser ui->textBrowser->setText( strInfo ); }

这个槽函数首先获取文件名,判断文件名是否为空。 对于非空文件名,定义文件对象 fileIn,并以只读模式打开文件,如果打开失败就报错返回。 在打开成功之后,定义 BMP 文件头 bfh 和信息头 bih,从文件中依次读取数据填充到这两个结构体中。 然后判断读取的字节数目够不够,如果读取的字节数不够,说明文件格式出错,就报错返回。 读取两个结构体对象 bfh 和 bih 字节数目够了,那进行下一步的判断: 先判断 bfh.bfType 是否为 0x4D42,如果不是这个数值说明不是 BMP 图片文件,填充信息到 strInfo 。 如果 bfh.bfType 是 0x4D42,说明这个文件是 BMP 格式的,我们开始逐个读取信息头 bih 里面的字段, 对于宽度、高度、颜色深度、帧数直接用对应字段数值即可; 水平和垂直分辨率比较特殊,BMP 里面单位是 像素点数每米,而操作系统里常见的是 DPI,是每英尺的点数。 通常这两个单位转换就是把 BMP 里的分辨率数值除以 39.3701 ,就得到 DPI,代码里可以看到简单转换的过程。 第二个例子的代码就是上面那么多,我们生成并运行例子,打开 logo.bmp ,点击 "显示图片" 和 "读取头部" 按钮,可以看到程序解析 BMP 头部结果:

读者可以把例子程序结果与 Windows 系统里面的 BMP 文件属性做对比。 本节示例代码中 QFile 对象都没有调用过 close() 函数,因为这些 QFile 对象是在函数栈上分配的,函数调用结束时,文件对象会自动销毁,就在对象销毁时文件自动被关闭,不需要手动调用 close() 函数。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3